/*
根据用户输入的文件路径，递归搜索下级目录下的所有文件
    判断输入的文件路径是否正确：
        正确输入
        路径不存在（要求重输）
        q（代表退出）
        空字符（包括前面输入的空格、制表符，默认设为当前路径）
    递归搜索：
        构造搜索路径"c:\Users\shadow\Desktop\*"
        调用FindFirstFile、FindNextFile函数搜索
        构造全路径"c:\Users\shadow\Desktop\autoexe.bat"

FindFirstFile、FindNextFile、FindClose函数
GetCurrentDirectory函数
_tstat函数、stat.h库
自定义函数实现获取用户输入
_tcscmp函数、_tprintf函数
memset函数、fflush函数、getchar函数

无法处理这种形式的输入uv\ntxt\ndw\n
*/
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <sys/stat.h>
#include <errno.h>

#define ALLFILES "\\*"
#define QUITFLAG "q"
// 输入的最大字符数
#define INPUTLEN MAX_PATH
// 搜索目录的层级
#define DIRLEVEL 1

int UserScanf(PTSTR FilePath, int iMaxLen);
int GetFilePath(PTSTR FilePath);
void FormatFilePath(PTSTR FilePath, int iLen);
void EnumFileInDir(PCTSTR ParentPath, PCTSTR SubPath, int iLevelDefine);
int GetLevelNum(PCTSTR ParentPath, PCTSTR SubPath);
void PrintTab(int iLevelCnt);

int iLevelCnt;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
    TCHAR FilePath[MAX_PATH] = {'\0'};

    if (GetFilePath(FilePath)) {
        _tprintf(TEXT("\n\n========================================\n"));
        // 统一去除路径最后的"\\"
        FormatFilePath(FilePath, _tcslen(FilePath));
        // 递归搜索
        EnumFileInDir(FilePath, FilePath, DIRLEVEL);

        _tprintf(TEXT("========================================\n"));
    }

    return 0;
}

/* 获取输入的路径（文件或目录） */
int GetFilePath(PTSTR FilePath)
{
    struct _tstat Infor;
    int iInputResult;

    _tprintf(TEXT("请输入要搜索的目录路径："));

    iInputResult = UserScanf(FilePath, INPUTLEN);

    // 判断输入的路径是否有效
    // 有效路径：截取最后的"\"
    //      普通路径（c:\Users\和c:\Users形式均有效）
    //      根路径（c:\形式有效，c:形式不合法）
    //      文件路径
    // 空路径（无输入有效字符）
    // 输入q，退出程序
    // 路径不存在，提示重输

    // 路径不存在，且不等于空路径和退出标志"q"
    while ((_tstat(FilePath, &Infor)==-1) && (iInputResult!=0) && (_tcscmp(FilePath, QUITFLAG)!=0)) {

        if (errno==ENOENT) {
            _tprintf(TEXT("输入的路径\"%s\"不存在。\n"), FilePath);
        } else if (errno==EINVAL) {
            _tprintf(TEXT("输入参数有误。\n"));
        } else {
            _tprintf(TEXT("_tstat函数遇到未知错误。\n"));
        }
                
        _tprintf(TEXT("请输入要搜索的目录路径："));
        memset(FilePath, '\0', MAX_PATH);
        iInputResult = UserScanf(FilePath, INPUTLEN);
    }

    if ((iInputResult==0)) {
        _tprintf(TEXT("输入是空字符串。程序默认把文件路径设置为当前路径。\n"));
        if (GetCurrentDirectory(MAX_PATH, FilePath)==0) {
            _tprintf(TEXT("但获取当前目录的路径出错。程序退出。\n"));
            return 0;
        }
    } else if (_tcscmp(FilePath, QUITFLAG)==0) {
        _tprintf(TEXT("你输入了\"%s\"，程序退出。\n"), FilePath);
        return 0;
    }

    return 1;
}

/* 统一去除路径最后的"\\" */
void FormatFilePath(PTSTR FilePath, int iLen)
{
    int iCnt = 0;

    while (*FilePath!='\0') {
        if ((*FilePath=='\\') && (iCnt==iLen-1)) {
            *FilePath = '\0';
        }
        FilePath++;
        iCnt++;
    }
}


/* 构造自定义的输入函数 */
int UserScanf(PTSTR FilePath, int iMaxLen)
{
    // 理想状态下的输入：txt，返回值：1
    // 输入"q"，返回值：1
    // 输入"  \ttxt\t  \t"，去除前后的空字符，返回值：1
    // 输入"\n"或"  \t\n"，返回值：0
    // 输入长度超过指定长度，截取前面有效字符
    char ch;
    int iCharValidCnt = 0;

    // 清空输入缓冲区
    // 防止前面的文件路径误输入影响后面的后缀输入
    fflush(stdin);

    // 跳过输入缓冲区里前面的无效字符：空格、制表符
    while ((ch=getchar())=='\t' || (ch==' ')) {
        ;
    }

    // 排除空字符串，诸如此类的无效输入"   \t  \n"  
    if (ch=='\n') {
        return 0;
    }

    // 将输入缓冲区中间的有效字符写入FilePath字符数组
    while ((iCharValidCnt<iMaxLen) && (ch!=EOF) && (ch!='\n')) {
        *FilePath++ = ch;
        iCharValidCnt++;
        ch=getchar();
    }

    // 将指针后退1个单位
    FilePath -= 1;

    // 将FilePath字符数组最后面的空格、制表符填为\0
    // FilePath字符数组中间的空格、制表符不更改
    while ((ch=*FilePath)==' ' || (ch=='\t')) {
        *FilePath-- = '\0';
    }

    return 1;
}

/*
依据最上层目录和下层目录或文件的反斜杠数量，判断层级，再按照指定的层级遍历文件
第1个参数ParentPath，最上层的目录，保持不变
第2个参数SubPath，下层目录
*/
void EnumFileInDir(PCTSTR ParentPath, PCTSTR SubPath, int iLevelDefine)
{
    // 搜索路径"c:\Users\shadow\Desktop\*"
    TCHAR SearchFilePath[MAX_PATH] = {'\0'};
    // 全路径"c:\Users\shadow\Desktop\temp"
    TCHAR FullPath[MAX_PATH] = {'\0'};
    // 依据最上层目录和下层目录或文件的反斜杠数量，判断层级
    int iLevelCnt = GetLevelNum(ParentPath, SubPath);
    // 统计层级
    WIN32_FIND_DATA FindFileData;

    // 构造搜索字符串"c:\Users\shadow\Desktop\*"
    _tcscpy_s(SearchFilePath, MAX_PATH, SubPath);  
    _tcscat(SearchFilePath, ALLFILES);

    HANDLE hDir = FindFirstFile(SearchFilePath, &FindFileData);

    // 根据层级，打印制制符，体现上下层级
    PrintTab(iLevelCnt);

    _tprintf(TEXT("\"%s\"目录下的文件：\n"), SubPath);

    if (iLevelCnt<=iLevelDefine) {
        if (hDir!=INVALID_HANDLE_VALUE) {
            do {
                // 排除.和..的目录
                if ((_tcscmp(FindFileData.cFileName, ".")==0) || (_tcscmp(FindFileData.cFileName, "..")==0)) {
                    continue;
                }

                // 构造全路径"c:\Users\shadow\Desktop\temp"
                wsprintf(FullPath, TEXT("%s\\%s"), SubPath, FindFileData.cFileName);

                // 依据层级，打印制制符，体现上下层级
                PrintTab(iLevelCnt+1);
                // 以全路径的形式打印目录下的文件夹及文件
                _tprintf(TEXT("%s\t"), FullPath);

                // 递归搜索下层目录
                if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
                    _tprintf(TEXT("<DIR>\n"));
                    EnumFileInDir(ParentPath, FullPath, iLevelDefine);
                } else {
                    _tprintf(TEXT("\n"));
                }
            } while (FindNextFile(hDir, &FindFileData));
        } else {
            _tprintf(TEXT("搜索文件出错。错误代码是：。%lu\n"), GetLastError());
            exit(0);
    }
    } else {
        PrintTab(iLevelCnt+1);
        _tprintf(TEXT("指定的层级是%d。无法搜索位于\"%s\"下的文件。\n"), iLevelDefine, SubPath);
    }

}

/* 依据最上层目录和下层目录或文件的反斜杠数量，判断层级 */
int GetLevelNum(PCTSTR ParentPath, PCTSTR SubPath)
{
    int iParentPathSlash = 0, iSubPathSlash = 0;

    while (*ParentPath!='\0') {
        if (*(ParentPath++)=='\\') {
            iParentPathSlash++;
        }  
    }

    while (*SubPath!='\0') {
        if (*(SubPath++)=='\\') {
            iSubPathSlash++;
        }
    }

    return (iSubPathSlash-iParentPathSlash);
}

/* 依据层级，打印制表符 */
void PrintTab(int iLevelCnt)
{
    for (int i=0; i<iLevelCnt; i++) {
        _tprintf(TEXT("\t"));
    }    
}